package nats

import (
	"encoding/json"
	"fmt"
	"github.com/wutianze/nats.go"
	"gluenet/modules/manager/server/infra"
	"gluenet/modules/manager/server/infra/prepare"
	"log"
	"os"
	"reflect"
	"strings"
)

const managerTopicPrefix = `v2.rpc.apis`

var Ctrl *nats.Conn

func Install() error {
	// 链接控制中心
	mode := os.Getenv("MODE")

	natsUrl := "nats://39.101.140.145:4222"
	if mode == "prod" {
		natsUrl = os.Getenv("NATS_SERVER")
	}

	nc, err := nats.Connect(natsUrl)
	if err != nil {
		return err
	}
	// 监听数据推送
	if _, err = nc.ISubscribe("topict.recive", func(msg *nats.Msg) {
		// todo deal the msg here

	}); err != nil {
		return err
	}

	Ctrl = nc

	cs := prepare.FetchAllNatsControllers()
	for k, v := range cs {
		ks := strings.Split(k.String(), ".")
		name := strings.ToLower(ks[len(ks)-1])
		bindController(v, name)
	}

	return nil
}

func bindController(obj interface{}, name string) error {
	if reflect.TypeOf(obj).Kind() != reflect.Ptr {
		return fmt.Errorf("object must be pointer to struct")
	}
	if reflect.ValueOf(obj).Elem().Kind() != reflect.Struct {
		return fmt.Errorf("object must be pointer to struct")
	}

	for i := 0; i < reflect.TypeOf(obj).NumMethod(); i++ {
		if reflect.TypeOf(obj).Method(i).Type.NumOut() > 1 {
			return fmt.Errorf("method out put must less or equal 1")
		}
		if reflect.TypeOf(obj).Method(i).Type.NumIn() > 2 {
			return fmt.Errorf("method out put must less or equal 2")
		}

	}

	for i := 0; i < reflect.TypeOf(obj).NumMethod(); i++ {
		suffix := strings.ToLower(reflect.TypeOf(obj).Method(i).Name)
		f := reflect.TypeOf(obj).Method(i)
		topic := strings.Join([]string{managerTopicPrefix, name, suffix}, ".")
		fmt.Printf("topic: %v\n", topic)
		_, err := Ctrl.ISubscribe(topic, func(msg *nats.Msg) {
			log.Printf("RPC: %v RECIVE", topic)

			//defer func() {
			//	if r := recover(); r != nil {
			//		fmt.Printf("Recovered in f %v, err %v\n", suffix, r)
			//	}
			//}()

			var input = []reflect.Value{reflect.ValueOf(obj)}
			if f.Type.NumIn() > 1 {
				switch f.Type.In(1).Kind() {
				case reflect.Slice:
					input = append(input, reflect.ValueOf(msg.Data))
				case reflect.String:
					input = append(input, reflect.ValueOf(string(msg.Data)))
				case reflect.Ptr:
					var in = reflect.New(f.Type.In(1).Elem())
					if err := json.Unmarshal(msg.Data, in.Interface()); err != nil {
						bts, _ := json.Marshal(infra.ResponseReqErr(fmt.Errorf("request topic: %v %v", topic, err), nil))
						msg.IRespond(bts)
						return
					}
					input = append(input, in)
				case reflect.Struct:
					var in = reflect.New(f.Type.In(1))
					if err := json.Unmarshal(msg.Data, in.Interface()); err != nil {
						bts, _ := json.Marshal(infra.ResponseReqErr(fmt.Errorf("request topic: %v %v", topic, err), nil))
						msg.IRespond(bts)
						return
					}
					input = append(input, in.Elem())
				}
			}

			res := f.Func.Call(input)
			if len(res) != 0 {
				bts, _ := json.Marshal(res[0].Interface())
				msg.IRespond(bts)
			}
		})
		if err != nil {
			panic(err)
		}

		infra.RegisterRouter("nats", topic)
	}

	return nil
}
